package icasue.tools.sqlhelper.core;

import icasue.tools.sqlhelper.core.AbstractSqlProviderFrame;
import icasue.tools.sqlhelper.core.DBTab;
import icasue.tools.sqlhelper.core.DBCol;
import lombok.SneakyThrows;
import icasue.reflect.annotations.NotNull;
import icasue.reflect.handles.classes.ClassOF;
import icasue.reflect.handles.fields.FieldOF;
import icasue.tools.sqlhelper.annotation.IndexG;
import icasue.reflect.handles.object.ObjectOF;
import icasue.reflect.handles.predicate.SureOF;
import icasue.tools.sqlhelper.annotation.Index;
import icasue.tools.sqlhelper.annotation.Primary;
import icasue.tools.sqlhelper.annotation.Unique;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;

/**
 * AbstractSqlProviderFrame 使用.
 */
class Frame{
    /**
     * 默认的数据库属性映射函数 (驼峰 -> _)  ex: (userId -> user_id)
     */
    static final Function<Field,String> $defaultMapping = (jField) -> {
        if(jField != null){
            // format db_col mapping item.
            StringBuilder knife = new StringBuilder();
            for (char chr : jField.getName().toCharArray()) {
                if((int)chr >= 65 && (int)chr <= 90)
                    knife.append((char) 95).append((char) (chr + 32));
                else knife.append(chr);
            }
            return knife.toString();
        }
        return null;
    };


    /**
     * previous init, to discovery this frame's JavaType and ColMarker ParameterizedType Class,
     * only consider relationship of extends!
     * @param frame
     * @return
     */
    static final Class[] _parse(AbstractSqlProviderFrame frame){
        Class[] typeParameterizedType = $$typeParameterizedDiscover(frame, AbstractSqlProviderFrame.class);
        SureOF.notNullAll(typeParameterizedType, "Con't discovery all enhancer ParameterizedTypes by relationship of extends.");
        return typeParameterizedType;
    }

    /**
     * final Init方法, 负责为Frame对象创建不可变的属性映射关系,在实例化属性赋值后调用.
     * @param frame
     */
    static final DBTab _init(AbstractSqlProviderFrame frame){
        SureOF.notNull_.accept(frame.javaType);
        SureOF.notNull_.accept(frame.markedAnnotationType);

        // create db table inst.
        final DBTab dbTab = new DBTab();

        // filling mixed index groups.
        dbTab.indexGrp = (IndexG[]) frame.javaType.getDeclaredAnnotationsByType(IndexG.class);

        // filling dbCol and java fields root mapping.
        if(Boolean.TRUE){
            // using default mapping strategy.
            if(!ObjectOF.equals_.test(frame.markedAnnotationType, Annotation.class)){
                for (Class<?> currentCls = frame.javaType;
                     currentCls != null && currentCls != Object.class;
                    // discovery mapping fields by recursion or only in direct JavaType TypeParameter.
                     currentCls = frame.recursionDiscoveryFields() ? currentCls.getSuperclass() : null){

                    Stream.of(currentCls.getDeclaredFields())
                            .filter(field -> field.getAnnotation(frame.markedAnnotationType) != null)
                            .filter(field -> !Modifier.isStatic(Field.class.cast(field).getModifiers()))
                            .peek(field -> Field.class.cast(field).setAccessible(true))
                            .map(field -> new Object[]{frame.mappingOnMarkAnnotation((Field)field, ((Field)field).getAnnotation(frame.markedAnnotationType)), field})
                            .forEach(col_field -> {
                                String column = (String) Object[].class.cast(col_field)[0];
                                Field field = (Field) Object[].class.cast(col_field)[1];

                                DBCol dbCol = DBCol.builder().field(field).column(column)
                                        .primary(field.getDeclaredAnnotation(Primary.class))
                                        .unique(field.getDeclaredAnnotation(Unique.class))
                                        .index(field.getDeclaredAnnotation(Index.class))
                                        .build();
                                dbTab.mappingRoot.put(column, dbCol);
                            });

                }
            }
            // using reflect.annotations's method discovery strategy.
            else {
                for (Class<?> currentCls = frame.javaType;
                     currentCls != null && currentCls != Object.class;
                     // discovery mapping fields by recursion or only in direct JavaType TypeParameter.
                     currentCls = frame.recursionDiscoveryFields() ? currentCls.getSuperclass() : null){

                    final Class<?> currentClsCopy = currentCls;
                    Stream.of(currentCls.getDeclaredFields())
                            .filter(field -> !Modifier.isStatic(Field.class.cast(field).getModifiers()))
                            .peek(field -> field.setAccessible(true))
                            .map(field -> new Object[]{frame.mappingOnLackMarkAnnotation(field,field.getName(),field.getType(),currentClsCopy), field})
                            .forEach(col_field -> {
                                String column = (String) Object[].class.cast(col_field)[0];
                                Field field = (Field) Object[].class.cast(col_field)[1];

                                DBCol dbCol = DBCol.builder().field(field).column(column)
                                        .primary(field.getDeclaredAnnotation(Primary.class))
                                        .unique(field.getDeclaredAnnotation(Unique.class))
                                        .index(field.getDeclaredAnnotation(Index.class))
                                        .build();
                                dbTab.mappingRoot.put(column, dbCol);
                            });
                }
            }
        }

        // clear index type by priority level.
        dbTab.mappingRoot.values().stream().forEach(dbCol -> {
            if(dbCol.primary != null){
                dbCol.unique = null;
                dbCol.index = null;
            }else if(dbCol.unique != null){
                dbCol.index = null;
            }
        });

        // return dbTab.
        return dbTab;
    }


    /**
     * database mapping instance for JavaType's copy, this copy is selective,
     * only fields which can mapping to database's column will be copy!
     * @param frame
     * @param source
     * @return
     */
    @SneakyThrows(Throwable.class)
    static final Object _copySelective(@NotNull AbstractSqlProviderFrame frame, @NotNull Object source){
        SureOF.notNullAll_.accept(frame,source);
        ClassOF.isInstance_.test(frame.javaType, source);
        Object target = frame.javaType.newInstance();
        frame.dbTab.mappingRoot.values().stream()
                .map(dbCol -> dbCol.field)
                .forEach(field -> FieldOF.set_.accept(
                        field,
                        target,
                        FieldOF.get_.apply(field,source)
                ));
        return target;
    }


    /**
     * This method using to discovery ParameterizedTypes which been signed on the sub class,
     * notice: only consider relationship of extends!
     * @param obj
     * @param parameterizedTypeClass
     * @return
     */
    private static final Class[] $$typeParameterizedDiscover(@NotNull Object obj,@NotNull Class parameterizedTypeClass){
        // check instance.
        SureOF.equals(parameterizedTypeClass.isInstance(obj),Boolean.TRUE,"Discovery Obj must be instance of TypeClass.");

        // mark target type's parameterized position and symbols.
        TypeVariable[] typeParameters = parameterizedTypeClass.getTypeParameters();
        SureOF.equals(typeParameters.length > 0, Boolean.TRUE, "Target class has not any ParameterizedType symbol.");
        String[] typeNameSymbols = Stream.of(typeParameters).map(typeParam -> typeParam.getName()).toArray(String[]::new);

        // create [class, typeVarsName, beenSignedParameterizedType] seq.
        List<Class> sortClass = new ArrayList<>();
        List<String[]> sortTypeVars = new ArrayList<>();
        List<Type[]> sortGenSupperClassType = new ArrayList<>();
        Class currentClass = obj.getClass();
        do{
            sortClass.add(currentClass);
            sortTypeVars.add(Stream.of(currentClass.getTypeParameters()).map(typeVar -> typeVar.getName()).toArray(String[]::new));
            Type genericSuperclass = currentClass.getGenericSuperclass();
            if(genericSuperclass instanceof ParameterizedType){
                sortGenSupperClassType.add(ParameterizedType.class.cast(genericSuperclass).getActualTypeArguments());
            }else {
                sortGenSupperClassType.add(new Type[0]);
            }
            currentClass = currentClass.getSuperclass();
        }while (!currentClass.equals(parameterizedTypeClass));

        sortClass.add(parameterizedTypeClass);
        sortTypeVars.add(typeNameSymbols);
        sortGenSupperClassType.add(0,new Type[0]);

        // result.
        Class[] result = new Class[typeNameSymbols.length];

        // target type's actually type parameter.
        Type[] types = sortGenSupperClassType.get(sortGenSupperClassType.size() - 1);
        for (int i = 0; i < typeNameSymbols.length; i++) {
            Type type = types[i];
            if(type instanceof Class){
                result[i] = Class.class.cast(type);
            }else {
                String enhancerTypeSymbol = TypeVariable.class.cast(type).getName();
                // discovery by recursion.
                int deep = sortGenSupperClassType.size() - 1;
                while (deep > 0){
                    String[] typeVariables = sortTypeVars.get(--deep);
                    if(typeVariables.length != 0){
                        for (int j = 0; j < typeVariables.length; j++) {
                            if(typeVariables[j].equals(enhancerTypeSymbol)){
                                Type enhancerType = sortGenSupperClassType.get(deep)[j];
                                if(enhancerType instanceof Class){
                                    result[i] = Class.class.cast(enhancerType);
                                }else {
                                    enhancerTypeSymbol = TypeVariable.class.cast(enhancerType).getName();
                                }
                                break;
                            }
                        }

                    }
                }
            }
        }
        return result;
    }

}